跳到主要内容

SpringCloud Eureka 服务注册中心

Eureka 是什么

官方文档地址

Eureka 是一个服务注册中心,就是用于连接各个模块的中心,服务提供方与 Eureka 之间通过 “心跳” 机制进行监控,当某个服务提供方出现问题,Eureka 自然会把它从服务列表中剔除。这就实现了服务的自动注册、发现、状态监控。

Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka 的客户端连接到 Eureka Server 并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。

在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者、服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地 RPC 调用 RPC远程调用

框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何 RPC远程框架中,都会有一个注册中心用于存放服务地址相关信息(接口地址)

Eureka Server 和 Eureka Client

Eureka Server 提供服务注册服务

各个微服务节点通过配置启动后,会在 EurekaServer 中进行注册,这样 EurekaServer 中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。

Eureka Client 通过注册中心进行访问

它是一个 Java客户端,用于简化 Eureka Server 的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向 Eureka Server 发送心跳(默认周期为30秒)如果 Eureka Server 在多个心跳周期内没有接收到某个节点的心跳, EurekaServer 将会从服务注册表中把这个服务节点移除(默认 90 秒)

SpringCloudApplication 注解

@SpringCloudApplication 是一个注解的集合,其中包含 @SpringBootApplication

注解 @SpringCloudApplication 包括:

  • @SpringBootApplication
  • @EnableDiscoveryClient 将自己注册进服务中心
  • @EnableCircuitBreaker 启用熔断器

分别是 SpringBoot 注解、注册服务中心 Eureka 注解、断路器注解。对于 SpringCloud 来说,这是每一微服务必须应有的三个注解,所以才推出了 @SpringCloudApplication 这一注解集合。

创建 Eureka Server

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

添加配置文件

server:
port: 8761

eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
# 因为没有设置集群,所以这里的 registerWithEureka 和 fetchRegistry 都设置为 false
registerWithEureka: false # false表示不向注册中心注册自己
fetchRegistry: false # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
serviceUrl:
# 设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
#server:
# 关闭自我保护机制,保证不可用服务被及时剔除
#enable-self-preservation: false
#eviction-interval-timer-in-ms: 2000

如果是集群则改成如下这样

eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
registerWithEureka: true # false表示不向注册中心注册自己
fetchRegistry: true # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
serviceUrl:
# 填写另一个服务中心的地址
defaultZone: http://${eureka.instance.hostname}:7001/eureka/

启动 Eureka

@SpringBootApplication
@EnableEurekaServer // 新版可以省略,但是一般加上方便看
public class StudyEurekaApplication {

public static void main(String[] args) {
SpringApplication.run(StudyEurekaApplication.class, args);
}

}

使用 8761 端口就能进入 Eureka 服务了

创建 Eureka Client

这里演示 Customer 模块调用 Search 模块

首先两个模块都先引入依赖

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

然后两个模块的启动类的模块

@SpringBootApplication
@EnableEurekaClient // 注意,加上这个注解,启用服务发现
public class SearchApplication {
public static void main(String[] args) {
SpringApplication.run(SearchApplication.class, args);
}
}

Spring Cloud E 版本之后就不需要再写 @EnableEurekaClient 注解了,直接导入依赖,配置一下 application.yml文件即可,还有一个作用和它类似的注解 @EnableDiscoveryClient 这个注解作用和 @EnableEurekaClient 类似,只不过前者只适用于 Eureka,后者可以还可以将微服务注册到其他服务发现组件,例如 Zookeeper、Consul 等

也可以直接使用上面提到的 @SpringCloudApplication 注解

@SpringBootApplication
@EnableEurekaClient
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}

// 这里的 RestTemplate 是用来进行 http 请求的工具类
// 至于为啥在这里创建 Bean 是因为懒得单独创建一个类来创建 Bean []~( ̄▽ ̄)~*
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}

配置文件

消费者的配置

eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
instance-id: customer01 # 服务的唯一标识
# 注意是通过这个名字来区别不同模块的
spring:
application:
name: Customer
# 为了避免冲突,每个模块都要指定一个端口号
server:
port: 8080

搜索服务的配置

eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
instance-id: search01
spring:
application:
name: search
server:
port: 8081

注意:如果想要让服务做集群,相同服务的 spring.application.name 要相同,但是这个 instance-id 不能相同(实例 id,每个都服务都不一样)

如下,同一个 PAYMENT-SERVICE 服务,却有两个实例

提供服务(Search 模块)

@RestController
public class SearchController {
@GetMapping("/search")
public String search() {
return "this search model";
}
}

客户端配置模板

eureka:
client:
# 表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
# 是否从 EurekaServer 抓取已有的注册信息,默认为 true。单节点无所谓,集群必须设置为 true才能配合 ribbon使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka #单机版
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
instance:
instance-id: payment8001 # 服务的唯一标识
prefer-ip-address: true #访问路径可以显示IP地址
# Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
# lease-renewal-interval-in-seconds: 1
# Eureka 服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
# lease-expiration-duration-in-seconds: 2

通过 Eureka 调用模块

通过 Eureka 调用模块(Customer 模块)

方式一:通过 getNextServerFromEureka 方法查找服务名称对应的地址

@RestController
public class CustomerController {

@Autowired
private RestTemplate restTemplate;

// 因为使用 @Autowired idea 这里会报找不到的错误,所以使用 @Resource 代替
@Resource
private EurekaClient eurekaClient;

@GetMapping("/customer")
public String customer(){
// 1. 通过 eurekaClient 获取 search 模块的信息
// 注意,第一个参数是区分大小写的,Eureka 显示的全是大写,但是实际上服务名称是按 yml 配置文件那个
// 第二个参数是询问使用 https 还是 http
InstanceInfo search = eurekaClient.getNextServerFromEureka("search", false);

// 2. 取得访问地址
String url = search.getHomePageUrl();
System.out.println(url);

// 3. 通过 restTemplate 进行访问(HTTP),这里的 url 只是服务的路径,所以还需要加上路由
// getForObject 通过 GET 请求获得响应结果 第二个参数是返回值类型
String response = restTemplate.getForObject(url + "/search", String.class);

return "this is customer model use" + response;
}
}

方式二:如果配置了多台服务,可以配置负载均衡策略

注:因为这里是后来补充的内容,所以服务名有点不一样,能理解意思就好了

@Slf4j
@RestController
public class OrderController {

// 这里的 URL 可以直接写服务的名称(看上图)
public static final String PAYMENT_URL = "http://PAYMENT-SERVICE/";

@Autowired
private RestTemplate restTemplate;

@GetMapping("/consumer/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL + "payment/get/" + id, CommonResult.class, id);
}
}

然后配置负载均衡,在 RestTemplate 上加上 @LoadBalanced 注解,这里就是 Ribbon 的负载均衡,详细的看 Ribbon 的笔记

@Configuration
public class RestConfiguration {

@Bean
@LoadBalanced
public RestTemplate restTemplate() { return new RestTemplate(); }
}

服务发现 Discovery

服务注册管理中心的数据来源,其中的数据来源于 DiscoveryClient

@Slf4j
@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class PaymentController {
private final PaymentService paymentService;
private final DiscoveryClient discoveryClient;

@GetMapping("/payment/discovery")
public Object discovery() {
// getServices 获取服务名称(就是 spring.application.name 的那个)
List<String> services = discoveryClient.getServices();
for (String service : services) {
log.info("===========server:" + service);
}

// 获取服务下的实例
List<ServiceInstance> instances = discoveryClient.getInstances("PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId() + "=========" + instance.getInstanceId());
}

return this.discoveryClient;
}
}

然后再在启动类上加上这个 @EnableDiscoveryClient 注解

@EnableEurekaClient
@SpringBootApplication
@EnableDiscoveryClient
public class Payment01 {
public static void main(String[] args) {
SpringApplication.run(Payment01.class, args);
}
}

打印到控制台的结果为:

===========server:payment-service
===========server:eureka-server
PAYMENT-SERVICE=========payment8001
PAYMENT-SERVICE=========payment8002

注意这个导包不要导错了

import org.springframework.cloud.client.discovery.DiscoveryClient;

Eureka 自我保护机制

参考资料 Spring Cloud Eureka 自我保护机制 参考资料 Eureka自我保护模式——难点重点

在学习配置 Eureka 时会发现下面这样的警告,他表示当前触发了自动保护模式

什么是 Eureka 自我保护模式?

默认情况下,如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障(比如网络故障或频繁的启动关闭客户端),Eureka Server自动进入自我保护模式。不再剔除任何服务,当网络故障恢复后,该节点自动退出自我保护模式。

即某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存

它属于 CAP 理论里面的 AP 分支

它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。即好死不如赖活着

综上,自我保护模式是一种应对网络异常的安全保护措施。 它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服 务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让 Eureka 集群更加的健壮、稳定。

eureka:
server:
# 自我保护模式,当出现网络分区故障、频繁的开启关闭客户端、eureka 在短时间内丢失过多客户端时,会进入自我保护模式,
# 即一个服务长时间没有发送心跳,eureka也不会将其删除,默认为true
enable-self-preservation: true
#eureka server清理无效节点的时间间隔,默认60000毫秒,即60秒
eviction-interval-timer-in-ms: 60000
#阈值更新的时间间隔,单位为毫秒,默认为15 * 60 * 1000
renewal-threshold-update-interval-ms: 15 * 60 * 1000
#阈值因子,默认是0.85,如果阈值比最小值大,则自我保护模式开启
renewal-percent-threshold: 0.85
#清理任务程序被唤醒的时间间隔,清理过期的增量信息,单位为毫秒,默认为30 * 1000
delta-retention-timer-interval-in-ms: 30000

自我保护模式是一种对网络异常的安全保护措施。使用自我保护模式让Eureka集群更加的健壮、稳定。

一旦开启了保护机制,则服务注册中心维护的服务实例就不是那么准确了,此时我们可以使用 eureka.server.enable-self-preservation=false 来关闭保护机制,这样可以确保注册中心中不可用的实例被及时的剔除(不推荐)。

自我保护模式被激活的条件是:在 1 分钟后,Renews (last min) < Renews threshold

这两个参数的意思:

  • Renews threshold:Eureka Server 期望每分钟收到客户端实例续约的总数。
  • Renews (last min):Eureka Server 最后 1 分钟收到客户端实例续约的总数。

具体的值,我们可以在 Eureka Server 界面可以看到:

解决这种情况的方法主要有几种方式:

1、等待 Eureka Server 自动恢复:正常的情况下,等待网络恢复(或者没有频繁的启动与关闭实例)后,等待一段时间 Eureka Server 会自动关闭自我保护模式,但是如果它迟迟没有关闭该模式,那么便可以尝试手动关闭,如下。

2、重启 Eureka Server:通常而言,PRD 环境建议对 Eureka Server 做负载均衡,这样在依次关闭并开启 Eureka Server 后,无效的实例会被清除,并且不会对正常的使用照成影响。

3、关闭 Eureka 的自我保护模式

关闭 Eureka 的自我保护模式

可以使用 eureka.server.enable-self-preservation=false 来禁用自我保护模式,适合在开发/测试环境中使用,生产环境建议打开自我保护模式。

对于开发/测试环境的 Eureka Server,一般建议关闭它的自我保护模式,因为可能需要不断的开启与关闭实例,如果并未关闭自我保护模式,那么很容易就会触发自我保护模式,此时对调试会相对比较麻烦。

但是关闭自我保护模式,会有另外一个可能的问题,即隔一段时间后,可能会发生实例并未关闭,却无法通过网关访问了,此时很可能是由于网络问题,导致实例(或网关)与 Eureka Server 断开了连接,Eureka Server 已经将其注销(网络恢复后,实例并不会再次注册),此时重启 Eureka Server 节点或实例,并等待一小段时间即可。

关闭自我保护模式,可以在服务端和客户端配置。

服务端配置:
eureka:
server:
# 关闭自我保护机制
enable-self-preservation: false
#eureka server清理无效节点的时间间隔,默认60000毫秒,即60秒
eviction-interval-timer-in-ms: 60000 # 单位毫秒

客户端配置:
# 心跳检测检测与续约时间
# 测试时将值设置设置小些,保证服务关闭后注册中心能及时踢出服务
eureka:
instance:
# 每间隔1s,向服务端发送一次心跳,证明自己依然”存活“。
lease-renewal-interval-in-seconds: 1
# 告诉服务端,如果我2s之内没有给你发心跳,就代表我“死”了,请将我踢掉。
lease-expiration-duration-in-seconds: 2

Eureka 的健康检查

先给服务加上这个依赖

<!--状态监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

说明:在Status栏显示着UP,表示应用程序状态正常。其它取值DOWN、OUT_OF_SERVICE、UNKNOWN等,只有UP的微服务会被请求。

由于 Eureka Server 与 Eureka Client 之间使用心跳机制来确定 Eureka Client 的状态,默认情况下,服务器端与客户端的心跳保持正常,应用程序就会始终保持 “UP” 状态,所以微服务的 UP 并不能完全反应应用程序的状态。

Spring Boot Actuator 提供了 /health 端点,该端点可展示应用程序的健康信息,只有将该端点中的健康状态传播到 Eureka Server 就可以了,实现这点很简单,只需为微服务配置如下内容:

# 开启健康检查(需要spring-boot-starter-actuator依赖)
eureka:
client:
healthcheck:
enabled: true # 这里其实可以省略,因为引入依赖后默认就是 true
instance:
instance-id: payment8002
prefer-ip-address: true # 可以加上这个访问路径可以显示IP地址

然后就可以在客户端检查服务的健康状态

当鼠标移动到对应的服务名称上左下角会出现这个服务的地址

Eureka 的安全性

因为默认通过 Eureka 调用模块是无需密码的,所以这样就有点不安全,因此可以配置一下使之访问 Eureka 需要密码

首先需要先在服务端引入安全模块

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

编写适配器

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 忽略掉 /eureka/** 这个路径
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}

在配置文件中加上

spring:
security:
user:
name: root
password: root

然后再在客户端的配置文件上加上访问的账户密码(只需在访问 eureka访问地址上加入账户密码即可)

eureka:
client:
serviceUrl:
defaultZone: http://用户名:密码@localhost:8761/eureka/

Eureka 的高可用

如果程序正在运行,突然 Eureka 宕机了,微服务的心脏宕机,那全部服务都完蛋了,所以一般也需要对 Eureka 进行集群

注:如果消费者之前访问过一次被调用的模块,Eureka 的宕机不会影响到这个模块(因为消费者调用一次后自身有了缓存)如果消费者之前没有访问过,或者没有缓存就会造成功能不可用

高可用的原理就是让多台 Eureka 互相注册,相互守望

配置步骤

1、准备多台 Eureka 服务

2、让 Eureka 相互通信,因为默认配置了单机模式每个模块只会注册到其中一个 Eureka 中

修改下服务的配置文件

eureka:
instance:
hostname: localhost
client:
# 原本单机时这里是 false 表示不将自己注册到注册中心,现在需要改成 true
registerWithEureka: true # 将当前 Eureka 注册到另一台 Eureka 上
fetchRegistry: true # 从 Eureka 上拉取信息,单节点无所谓,集群必须设置为 true才能配合
serviceUrl:
# 这里需要填写另一台服务器的地址(别忘记加上账户密码)
defaultZone: http://root:root@${eureka.instance.hostname}:${server.port}/eureka/
# 注意:第二台 Eureka 服务也需要使用第一台的地址
# defaultZone: http://root:root@${eureka.instance.hostname}:8761/eureka/

3、让服务注册到多台 Eureka 服务中(就是在客户端上加多一个服务地址)

eureka:
client:
serviceUrl:
defaultZone: http://root:root@localhost:8761/eureka/,http://root:root@localhost:8762/eureka/